import pandas as pd
import torch

from algorithms.base.nsga2 import NSGA2
from config import device
from indicators import Indicator
from utils.sort import sortrows


class HV(Indicator):
    """
    %------------------------------- Reference --------------------------------
    % E. Zitzler and L. Thiele, Multiobjective evolutionary algorithms: A
    % comparative case study and the strength Pareto approach, IEEE
    % Transactions on Evolutionary Computation, 1999, 3(4): 257-271.
    """

    @staticmethod
    def isTheSmallerTheBetter():
        return False

    @classmethod
    def value(cls, pop_obj, optimal_solution):
        pop_obj = NSGA2.get_best(pop_obj)
        if pop_obj.shape[1] != optimal_solution.shape[1]:
            score = torch.nan
        else:
            N, M = pop_obj.shape
            obj_min = torch.min(torch.cat((pop_obj, torch.zeros((1, M))), dim=0), dim=0)[0]
            obj_max = torch.max(optimal_solution, dim=0)[0]
            pop_obj = (pop_obj - obj_min) / ((obj_max - obj_min) * 1.1)
            pop_obj = pop_obj[torch.all(pop_obj <= 1, dim=1)]
            RefPoint = torch.ones(M)
            if pop_obj.size(0) == 0:
                score = torch.tensor([0], device=device, dtype=torch.double)
            elif M < 4:
                # Calculate the exact HV value
                pl = sortrows(pop_obj)
                S = [[1, pl]]
                for k in range(M - 1):
                    S_ = []
                    for i in range(len(S)):
                        s_temp = cls.Slice(S[i][1], k, RefPoint)
                        for j in range(len(s_temp)):
                            temp = [s_temp[j][0] * S[i][0], s_temp[j][1]]
                            S_.append(temp)
                    S = S_
                score = 0
                for i in range(len(S)):
                    p = cls.Head(S[i][1])
                    score += (S[i][0] * torch.abs(p[M - 1] - RefPoint[M - 1]))
            else:
                # Estimate the HV value by Monte Carlo estimation
                SampleNum = int(1e6)
                MaxValue = RefPoint
                MinValue = torch.min(pop_obj, dim=0)[0]
                Samples = torch.rand((SampleNum, M)) * (MaxValue - MinValue) + MinValue
                for i in range(pop_obj.shape[0]):
                    domi = torch.ones(Samples.shape[0], dtype=torch.bool)
                    m = 0
                    while m < M and torch.any(domi):
                        domi = domi & (pop_obj[i, m] <= Samples[:, m])
                        m += 1
                    Samples = Samples[~domi, :]
                score = torch.prod(MaxValue - MinValue) * (1 - Samples.shape[0] / SampleNum)
        return score

    @classmethod
    def Slice(cls, pl, k, RefPoint):
        p = cls.Head(pl)
        pl = cls.tail(pl)
        ql = []
        S = []
        while len(pl) != 0:
            ql = cls.insert(p, k + 1, ql)
            p_ = cls.Head(pl)
            cell_ = [abs(p[k] - p_[k]), ql]
            S = cls.add(cell_, S)
            p = p_
            pl = cls.tail(pl)
        ql = cls.insert(p, k + 1, ql)
        cell_ = [abs(p[k] - RefPoint[k]), ql]
        S = cls.add(cell_, S)
        return S

    @classmethod
    def insert(cls, p, k, pl):
        flag1 = 0
        flag2 = 0
        ql = []
        hp = cls.Head(pl)
        while len(pl) != 0 and hp[k] < p[k]:
            ql.append(hp)
            pl = cls.tail(pl)
            hp = cls.Head(pl)
        ql.append(p)
        m = len(p)
        while len(pl) != 0:
            q = cls.Head(pl)
            for i in range(k - 1, m):
                if p[i] < q[i]:
                    flag1 = 1
                elif p[i] > q[i]:
                    flag2 = 1
            if not (flag1 == 1 and flag2 == 0):
                ql.append(cls.Head(pl))
            pl = cls.tail(pl)
        return torch.stack(ql)

    @staticmethod
    def Head(pl):
        if len(pl) != 0:
            return pl[0]
        else:
            return None

    @staticmethod
    def tail(pl):
        if len(pl) < 2:
            return []
        else:
            return pl[1:]

    @staticmethod
    def add(cell_, S):
        n = len(S)
        m = 0
        for k in range(n):
            if cell_[1].shape == S[k][1].shape and torch.all(cell_[1] == S[k][1]):
                S[k][0] += cell_[0]
                m = 1
                break
        if m == 0:
            S.append(cell_)
        return S


if __name__ == "__main__":
    # 读取 Excel 文件
    df = pd.read_excel('optimum.xlsx', header=None)

    # 将 DataFrame 转换为 PyTorch 张量
    optimum = torch.tensor(df.values)
    # 读取 Excel 文件
    df = pd.read_excel('PopObj.xlsx', header=None)

    # 将 DataFrame 转换为 PyTorch 张量
    PopObj = torch.tensor(df.values)
    res = HV.value(PopObj, optimum)
    print(res)
